package com.demo2do.fms.admin.service.business.impl;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;

import com.demo2do.core.persistence.GenericDaoSupport;
import com.demo2do.core.persistence.annotation.Dao;
import com.demo2do.core.utils.CollectionUtils;
import com.demo2do.fms.admin.dao.business.DispatchOrderDao;
import com.demo2do.fms.admin.dao.business.ExpenseSheetDaoSupport;
import com.demo2do.fms.admin.dao.business.SequenceDaoSupport;
import com.demo2do.fms.admin.dao.business.ShipmentDao;
import com.demo2do.fms.admin.dao.business.TransportCostDao;
import com.demo2do.fms.admin.entity.export.business.ExpenseSheet;
import com.demo2do.fms.admin.entity.export.business.ExpenseSheetFooter;
import com.demo2do.fms.admin.entity.export.business.ExpenseSheetHeader;
import com.demo2do.fms.admin.entity.export.business.ExpenseSheetItem;
import com.demo2do.fms.admin.entity.export.business.ExpenseSheetPage;
import com.demo2do.fms.admin.entity.search.business.ExpenseSheetSearch;
import com.demo2do.fms.admin.handler.support.FmsJdbcSupport;
import com.demo2do.fms.admin.service.business.ExpenseSheetService;
import com.demo2do.fms.entity.business.shipment.DispatchOrder;
import com.demo2do.fms.entity.business.shipment.TransportCost;
import com.demo2do.fms.entity.data.business.VehicleFeature;
import com.demo2do.fms.entity.vehicle.source.Vehicle;

/**
 *
 * @author Ark
 *
 */
@Service("expenseService")
public class ExpenseSheetServiceImpl implements ExpenseSheetService {
    
    @Autowired
    private GenericDaoSupport genericDaoSupport;
    
    @Dao
    private ShipmentDao shipmentDao;
    
    @Dao
    private DispatchOrderDao dispatchOrderDao;
    
    @Dao
    private TransportCostDao transportCostDao;
    
    @Autowired
    private ExpenseSheetDaoSupport expenseSheetDaoSupport;
    
    @Autowired
    private SequenceDaoSupport sequenceDaoSupport;
    
    @Autowired
    private FmsJdbcSupport fmsJdbcSupport;

    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.business.ExpenseService#countDispatchOrderRecord(com.demo2do.fms.admin.entity.search.business.ExpenseSheetSearch)
     */
    public int countDispatchOrderRecord(ExpenseSheetSearch search) {
        String sentence = search.buildCountTemplate();
        Map<String, Object> parameters = search.buildParameters();
        return fmsJdbcSupport.queryForInt(sentence, parameters);
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.business.ExpenseService#listDispatchOrderRecord(com.demo2do.fms.admin.entity.search.business.ExpenseSheetSearch, com.demo2do.core.entity.page.Page)
     */
    public List<Map<String, Object>> listDispatchOrderRecord(ExpenseSheetSearch search) {
        
        // 1. build sentence
        String sentence = search.buildSentenceTemplate();
        Map<String, Object> parameters = search.buildParameters();
        
        // 2. do query
        List<Map<String, Object>> result = fmsJdbcSupport.queryForList(sentence, parameters, search.buildRowMapper());
        return result;
        
    }

    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.business.ExpenseSheetService#getInternalOils(java.util.List)
     */
    public BigDecimal getInternalOils(List<Long> dispatchOrderIds) {
        
        // 1. 计算派车单总油耗
        BigDecimal totalOilUsage = BigDecimal.ZERO;
        
        List<TransportCost> transportCosts = transportCostDao.list(dispatchOrderIds);
        for (TransportCost transportCost : transportCosts) {
            BigDecimal oilUsage = transportCost.getOilUsage();
            totalOilUsage = totalOilUsage.add(oilUsage);
        }
        
        // 2. 该驳运车动力类型的内部加油量(油箱容量)
        DispatchOrder dispatchOrder = genericDaoSupport.load(DispatchOrder.class, dispatchOrderIds.get(0));
        
        // 2.1 获取该驳运车动力类型
        Long vehicleId = dispatchOrder.getVehicleId();
        Vehicle vehicle = genericDaoSupport.load(Vehicle.class, vehicleId);
        VehicleFeature vehicleFeature = vehicle.getVehicleFeature();
        
/*        // 2.2 该动力类型油箱容量
        BigDecimal internalOilUsage = vehicleFeature.getInternalOilUsage();
        
        // 3. 两者较小值为额定内部加油量
        int result = totalOilUsage.compareTo(internalOilUsage);
        
        return result < 0 ? totalOilUsage : internalOilUsage;
        */

        /*
        * 修改一次报销中勾选的派车单所涉及内部加油量的计算方法：
        * 如果驳运车动力类型中记录的油箱容积>1，则维持原算法不变；
        * 如果油箱容积<=1，则油箱容积变相转义为内部用油比例，即内部加油量=总用油*油箱容积(内部用油比例)。
        *
        */
        BigDecimal internalOilUsage = vehicleFeature.getInternalOilUsage();

        if (internalOilUsage.compareTo(BigDecimal.ONE) > 0){
            if (internalOilUsage.compareTo(totalOilUsage) > 0){
                return totalOilUsage;
            }
            return internalOilUsage;
        }else {
            return totalOilUsage.multiply(internalOilUsage);
        }

    }

    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.business.ExpenseSheetService#setupInternalOils(java.util.List, java.math.BigDecimal, java.math.BigDecimal, java.lang.Long)
     */
    public void setupInternalOils(List<Long> dispatchOrderIds, BigDecimal ratedOilUsage, BigDecimal actualOilUsageReal, Long operator) {
        
        // 加油地点固定为0, 数字精度固定为2
        DecimalFormat decimalFormat = new DecimalFormat("0.00");
        decimalFormat.setRoundingMode(RoundingMode.HALF_UP);
        long placeCode = 0L;
        
        // 1. each transportCost
        List<TransportCost> transportCosts = transportCostDao.list(dispatchOrderIds);
        for (TransportCost transportCost : transportCosts) {
            
            // 2. 每张派车单的用油量、用油变现量
            BigDecimal everyOilUsage = transportCost.getOilUsage(); 
            BigDecimal everyOilUsageCompen = transportCost.getOilUsage();
            
            // 3. 更新内部加油量、内部加油变现量
            BigDecimal internalOilUsage = BigDecimal.ZERO;    // 内部加油量
            BigDecimal internalOilUsageCompen = BigDecimal.ZERO;  // 内部加油变现量
            
            if (ratedOilUsage.compareTo(BigDecimal.ZERO) != 0 && ratedOilUsage.compareTo(everyOilUsage) == 1) {  // 额定内部加油量大于派车单油耗
            
                // 在额定用油量减去当前派车单的耗油量
                ratedOilUsage = ratedOilUsage.subtract(everyOilUsage);
                internalOilUsage = new BigDecimal(decimalFormat.format(everyOilUsage));
                
            } else {    // 额定内部加油量=0 || 额定内部加油量小于派车单油耗
                
                internalOilUsage = new BigDecimal(decimalFormat.format(ratedOilUsage));
                ratedOilUsage = BigDecimal.ZERO;
            }
                
            if (actualOilUsageReal.compareTo(BigDecimal.ZERO) != 0 && actualOilUsageReal.compareTo(everyOilUsageCompen) == 1) {
                
                actualOilUsageReal = actualOilUsageReal.subtract(everyOilUsageCompen);
                internalOilUsageCompen = new BigDecimal(decimalFormat.format(everyOilUsageCompen));
                
            } else {    // 实际内部加油量=0 || 实际内部加油量小于派车单内部加油变现量
                
                internalOilUsageCompen = new BigDecimal(decimalFormat.format(actualOilUsageReal));
                actualOilUsageReal = BigDecimal.ZERO;
            
            }
                
            // 4. 更新内部油变现费、外部油费、油费总报销金额   
            BigDecimal internalOilFeeCompensation = internalOilUsageCompen.multiply(transportCost.getInternalOilPrice()).divide(BigDecimal.ONE, 2, BigDecimal.ROUND_HALF_UP);
            BigDecimal externalOilFee = transportCost.getOilUsage().subtract(internalOilUsage).multiply(transportCost.getExternalOilPrice()).divide(BigDecimal.ONE, 2, BigDecimal.ROUND_HALF_UP);
            BigDecimal totalOilFeeReimbursed = internalOilFeeCompensation.add(externalOilFee);
                
            // 5.1 更新运输成本信息
            genericDaoSupport.update(transportCost.editOilUsage(placeCode, internalOilUsage, internalOilUsageCompen, internalOilFeeCompensation, externalOilFee, totalOilFeeReimbursed));
                
        }
        
        // 5.2 更新订单信息=最后操作人、时间
        dispatchOrderDao.setupInternalOils(dispatchOrderIds, operator, new Date());
        
    }
    
    /*
     * (non-Javadoc)
     * @see com.demo2do.fms.admin.service.business.ExpenseSheetService#listExpenseSheet(java.util.List, java.lang.Long)
     */
    public List<ExpenseSheet> listExpenseSheet(List<Long> dispatchOrderIds, Long operator) {
        
        List<ExpenseSheet> expenseSheets = new ArrayList<ExpenseSheet>();
        
        if(org.apache.commons.collections.CollectionUtils.isNotEmpty(dispatchOrderIds)) {
            
            // 1. 准备报销单头部
            Long firstDispatchOrderId = dispatchOrderIds.get(0);  // 获取第一张派车单并用于生成所有报销单的头部
            DispatchOrder dispatchOrder = genericDaoSupport.load(DispatchOrder.class, firstDispatchOrderId);
            TransportCost transportCost = transportCostDao.load(dispatchOrder.getId());
            
            ExpenseSheetHeader expenseSheetHeader = expenseSheetDaoSupport.prepareExpenseSheetHeader(dispatchOrder, transportCost);
            
            // 2.根据dispatchOrder数目计算总页数
            int totalPage = this.calculateTotalPages(dispatchOrderIds.size());
            
            // 3.List所有派车单和运输成本
            List<DispatchOrder> dispatchOrders = dispatchOrderDao.listByLeavingTime(dispatchOrderIds);
            List<TransportCost> transportCosts = transportCostDao.list(dispatchOrderIds);
            
            Map<String, TransportCost> transportCostMappings = CollectionUtils.constructMapValue(transportCosts, "dispatchOrderId");
            
            // 所有的item，用于计算footer
            List<ExpenseSheetItem> totalItems = new ArrayList<ExpenseSheetItem>();
            
            for(int i = 1; i <= totalPage; i++) {
                
                // 4.计算每一页的items
                int size = dispatchOrders.size();
                int start = ( i - 1 ) * 14 > size ? size : ( i - 1 ) * 14;
                int end = size < i * 14  ? size : i * 14;
                
                // 每一页item序号初始化
                int itemAmount = i == 1 ? 0 : (i - 1)*14;
                List<DispatchOrder> currentOrders = dispatchOrders.subList(start, end);
                List<ExpenseSheetItem> expenseSheetItems = expenseSheetDaoSupport.prepareExpenseSheetItem(currentOrders, transportCostMappings, itemAmount);
                
                // 5.计算每一页的其他费用明细
                String otherFeeDetail = expenseSheetDaoSupport.prepareOtherFeeDetail(currentOrders);
                
                // 创建报销单 
                ExpenseSheet expenseSheet = new ExpenseSheet(expenseSheetHeader, expenseSheetItems, otherFeeDetail, new ExpenseSheetPage(i, totalPage));
                
                // 所有的item，用于计算footer
                totalItems.addAll(expenseSheetItems);
                
                if(i == totalPage) {  // 判断表尾的情况
                    
                    // 6.1 根据总的dispatchOrder和transportCost计算expenseSheetFooter
                    ExpenseSheetFooter expenseSheetFooter = expenseSheetDaoSupport.prepareExpenseSheetFooter(dispatchOrders, transportCostMappings, totalItems);
                    
                    // 6.2 最后一条添加footer数据
                    expenseSheet.initialize(expenseSheetFooter);
                    
                }
                
                expenseSheets.add(expenseSheet);
                
            }
            
            
            // 7. update dispatcherOrders
            dispatchOrderDao.expenseSheetExport(dispatchOrderIds, new Date(), operator, expenseSheetHeader.getSenquenceCode());
            
        }
        
        return expenseSheets;
    }
    
    /**
     * 
     * @param dispatchOrderCount
     * @return
     */
    private int calculateTotalPages(int dispatchOrderCount) {
        int totalPage = 0;
        if(dispatchOrderCount < 14) {
            totalPage = dispatchOrderCount > 10 ? 2 : 1;
        } else if(dispatchOrderCount % 14 > 10) {
            totalPage = dispatchOrderCount / 14 + 2;
        } else {
            totalPage = dispatchOrderCount / 14 + 1;
        }
        return totalPage;
    }
    
    /*
     * (non-Javadoc)
     * @see com.demo2do.fms.admin.service.business.ExpenseSheetService#listOilPrice(java.util.List)
     */
    public List<Map<String, Object>> listOilPrice(List<Long> dispatchOrderIds) {
        
        // 查询语句
        String sentence = "SELECT v.vehicle_code || ' - ' || v.vehicle_name AS vehicleCode, " +
                                  "CASE tc.oil_type_id WHEN 3 THEN '0#' WHEN 4 THEN '10#' END AS oilType, " +
                                  "CASE tc.internal_oil_place WHEN 91 THEN '\u9756\u6c5f' ELSE '\u4e0a\u6d77' END AS oilPlace, " +
                                  "(tc.internal_oil_usage - tc.internal_oil_usage_compen) AS oilUsage, " + 
                                  "c.organization_name AS organizationName, " + 
                                  "c.id AS organizationId " + 
                             "FROM vw_vehicle v, tbl_truck_dispatch_order o, vw_transportation_company c, tbl_truck_transport_cost tc, tbl_all_master_data place " + 
                             "WHERE v.id = o.vehicle_id AND o.id = tc.dispatch_order_id AND tc.internal_oil_place = place.code AND place.type = 'OIL_PLACE' AND c.id = o.organization_id " +
                                    "AND o.id IN (:dispatchOrderIds)";
        
        RowMapper<Map<String, Object>> rowMapper = this.buildOilPriceRowMapper();
        
        Map<String, Object> parameters = new HashMap<>();
        parameters.put("dispatchOrderIds", dispatchOrderIds);
          
        return fmsJdbcSupport.queryForList(sentence, parameters, rowMapper);
        
    }
    
    /**
     * 
     * @return
     */
    public RowMapper<Map<String, Object>> buildOilPriceRowMapper() {
        
        RowMapper<Map<String, Object>> rowMapper = new RowMapper<Map<String,Object>>() {

            /* (non-Javadoc)
             * @see org.springframework.jdbc.core.RowMapper#mapRow(java.sql.ResultSet, int)
             */
            public Map<String, Object> mapRow(ResultSet rs, int rowNum) throws SQLException {
                
                Map<String, Object> result = new HashMap<String, Object>();
                
                result.put("vehicleCode", rs.getObject("vehicleCode"));
                result.put("oilType", rs.getObject("oilType"));
                result.put("oilPlace", rs.getObject("oilPlace"));
                result.put("oilUsage", rs.getObject("oilUsage"));
                result.put("organizationName", rs.getObject("organizationName"));
                result.put("organizationId", rs.getObject("organizationId"));
                
                return result;
                
            }
            
        };
        
        return rowMapper;
        
    }
    
    /*
     * (non-Javadoc)
     * @see com.demo2do.fms.admin.service.business.ExpenseSheetService#getOilPriceSequence(java.lang.String)
     */
    public String getOilPriceSequence(String companyCode) {
        return sequenceDaoSupport.getSequence(companyCode, "OIL_TICKET_CODE");
    }
    
}
